/*
 * Copyright (c) 2024 Shenzhen Kaihong Digital Industry Development Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { DfuLogListener } from './DfuLogListener'
import { DfuBaseService } from './DfuBaseService'
import { DfuProgressListener } from './DfuProgressListener'
import { GattError } from './error/GattError'
import { BootloaderScannerFactory } from './internal/scanner/BootloaderScannerFactory'
import { commonEventManager } from '@kit.BasicServicesKit';
import { emitter } from '@kit.BasicServicesKit';
import hilog from '@ohos.hilog';

class LogBroadcastReceiver {
  private TAG: string = "LogBroadcastReceiver";
  private DOMAIN: number = 0x8632;
  private mListeners: Map<string, DfuLogListener> = new Map<string, DfuLogListener>();
  private mGlobalLogListener: DfuLogListener;

  constructor() {
    emitter.on("dfulog", (eventData: emitter.EventData) => {
      hilog.info(this.DOMAIN, this.TAG, `on dfulog: ${JSON.stringify(eventData)}`);

      let address: string = eventData.data.address;
      let logmsg: string = eventData.data.log;
      this.onReceive(address, logmsg);
    })
  }

  dispose() {
    emitter.off("dfulog");
  }

  public setLogListener(globalLogListener: DfuLogListener) {
    this.mGlobalLogListener = globalLogListener;
  }

  public setLogListenerByMac(deviceAddress: string, listener: DfuLogListener) {
    // When using the buttonless update and updating the SoftDevice the application will
    // be removed to make space for the new SoftDevice.
    // The new bootloader will afterwards advertise with the address incremented by 1.
    // We need to make sure that the listener will receive also events from this device.
    this.mListeners.set(deviceAddress, listener);
    this.mListeners.set(BootloaderScannerFactory.getIncrementedAddress(deviceAddress), listener); // assuming the address is a valid BLE address
  }

  public removeLogListener(listener: DfuLogListener): boolean {
    if (this.mGlobalLogListener == listener)
      this.mGlobalLogListener = null;

    // We do it 2 times as the listener was added for 2 addresses
    for (let [key, value] of this.mListeners) {
      if (value == listener) {
        this.mListeners.delete(key);
        listener.dispose();
        break;
      }
    }
    for (let [key, value] of this.mListeners) {
      if (value == listener) {
        this.mListeners.delete(key);
        listener.dispose();
        break;
      }
    }

    return this.mGlobalLogListener == null && this.mListeners.size == 0;
  }

  public onReceive(address: string, message: string) {
    let globalListener: DfuLogListener = this.mGlobalLogListener;
    let deviceListener: DfuLogListener = this.mListeners.get(address);

    if (globalListener == null && deviceListener == null) {
      hilog.error(this.DOMAIN, this.TAG, 'globalListener and deviceListener are null');
      return;
    }

    if (globalListener != null) {
      globalListener.onLogEvent(address, DfuBaseService.LOG_LEVEL_INFO, message);
    }
    if (deviceListener != null) {
      deviceListener.onLogEvent(address, DfuBaseService.LOG_LEVEL_INFO, message);
    }
  }
}

class ProgressBroadcastsReceiver {
  private TAG: string = "ProgressBroadcastsReceiver";
  private DOMAIN: number = 0x8632;
  private mListeners: Map<string, DfuProgressListener> = new Map<string, DfuProgressListener>();
  private mGlobalProgressListener: DfuProgressListener;
  private subscribeInfo:commonEventManager.CommonEventSubscribeInfo = {
    events: ["event"]
  };

  constructor() {
    emitter.on("dfuprogress", (eventData: emitter.EventData) => {
      hilog.info(this.DOMAIN, this.TAG, `on dfuprogress: ${JSON.stringify(eventData)}`);

      let address: string = eventData.data.address;
      let action: string = eventData.data.action;
      let progress: number = eventData.data.progress;
      let speed: number = eventData.data.speed;
      let avgSpeed: number = eventData.data.avgSpeed;
      let currentPart: number = eventData.data.currentPart;
      let partsTotal: number = eventData.data.partsTotal;
      let error: number = eventData.data.error;
      let errorType: number = eventData.data.errorType;

      this.onReceive(address, action, progress, speed, avgSpeed, currentPart, partsTotal, error, errorType);
    })
  }

  dispose() {
    emitter.off("dfumsg");
  }

  public setProgressListener(globalProgressListener: DfuProgressListener) {
    this.mGlobalProgressListener = globalProgressListener;
  }

  public setProgressListenerByMac(deviceAddress: string, listener: DfuProgressListener) {
    // When using the buttonless update and updating the SoftDevice the application will be removed to make space for the new SoftDevice.
    // The new bootloader will afterwards advertise with the address incremented by 1. We need to make sure that the listener will receive also events from this device.
    this.mListeners.set(deviceAddress, listener);
    this.mListeners.set(BootloaderScannerFactory.getIncrementedAddress(deviceAddress), listener); // assuming the address is a valid BLE address
  }

  public removeProgressListener(listener: DfuProgressListener): boolean {
    if (this.mGlobalProgressListener == listener) {
      this.mGlobalProgressListener = null;
    }

    // We do it 2 times as the listener was added for 2 addresses
    for (let [key, value] of this.mListeners) {
      if (value == listener) {
        this.mListeners.delete(key);
        listener.dispose();
        break;
      }
    }
    for (let [key, value] of this.mListeners) {
      if (value == listener) {
        this.mListeners.delete(key);
        listener.dispose();
        break;
      }
    }

    return this.mGlobalProgressListener == null && this.mListeners.size == 0;
  }

  public onReceive(address: string, action: string, progress: number, speed: number, avgSpeed: number,
    currentPart: number, partsTotal: number, error: number, errorType: number) {
    if (address == null)
      return;

    // Find proper listeners
    let globalListener: DfuProgressListener = this.mGlobalProgressListener;
    let deviceListener: DfuProgressListener = this.mListeners.get(address);

    if (globalListener == null && deviceListener == null) {
      return;
    }

    if (action == null)
      return;

    switch (action) {
      case DfuBaseService.BROADCAST_PROGRESS: {
        switch (progress) {
          case DfuBaseService.PROGRESS_CONNECTING:
            if (globalListener != null)
              globalListener.onDeviceConnecting(address);
            if (deviceListener != null)
              deviceListener.onDeviceConnecting(address);
            break;
          case DfuBaseService.PROGRESS_STARTING:
            if (globalListener != null) {
              globalListener.onDeviceConnected(address);
              globalListener.onDfuProcessStarting(address);
            }
            if (deviceListener != null) {
              deviceListener.onDeviceConnected(address);
              deviceListener.onDfuProcessStarting(address);
            }
            break;
          case DfuBaseService.PROGRESS_ENABLING_DFU_MODE:
            if (globalListener != null)
              globalListener.onEnablingDfuMode(address);
            if (deviceListener != null)
              deviceListener.onEnablingDfuMode(address);
            break;
          case DfuBaseService.PROGRESS_VALIDATING:
            if (globalListener != null)
              globalListener.onFirmwareValidating(address);
            if (deviceListener != null)
              deviceListener.onFirmwareValidating(address);
            break;
          case DfuBaseService.PROGRESS_DISCONNECTING:
            if (globalListener != null)
              globalListener.onDeviceDisconnecting(address);
            if (deviceListener != null)
              deviceListener.onDeviceDisconnecting(address);
            break;
          case DfuBaseService.PROGRESS_COMPLETED:
            if (globalListener != null) {
              globalListener.onDeviceDisconnected(address);
              globalListener.onDfuCompleted(address);
            }
            if (deviceListener != null) {
              deviceListener.onDeviceDisconnected(address);
              deviceListener.onDfuCompleted(address);
            }
            break;
          case DfuBaseService.PROGRESS_ABORTED:
            if (globalListener != null) {
              globalListener.onDeviceDisconnected(address);
              globalListener.onDfuAborted(address);
            }
            if (deviceListener != null) {
              deviceListener.onDeviceDisconnected(address);
              deviceListener.onDfuAborted(address);
            }
            break;
          default:
            if (progress == 0) {
              if (globalListener != null)
                globalListener.onDfuProcessStarted(address);
              if (deviceListener != null)
                deviceListener.onDfuProcessStarted(address);
            }
            if (globalListener != null)
              globalListener.onProgressChanged(address, progress, speed, avgSpeed, currentPart, partsTotal);
            if (deviceListener != null)
              deviceListener.onProgressChanged(address, progress, speed, avgSpeed, currentPart, partsTotal);
            break;
        }

        break;
      }
      case DfuBaseService.BROADCAST_ERROR: {
        if (globalListener != null)
          globalListener.onDeviceDisconnected(address);
        if (deviceListener != null)
          deviceListener.onDeviceDisconnected(address);
        switch (errorType) {
          case DfuBaseService.ERROR_TYPE_COMMUNICATION_STATE:
            if (globalListener != null)
              globalListener.onError(address, error, errorType, GattError.parseConnectionError(error));
            if (deviceListener != null)
              deviceListener.onError(address, error, errorType, GattError.parseConnectionError(error));
            break;
          case DfuBaseService.ERROR_TYPE_DFU_REMOTE:
            if (globalListener != null)
              globalListener.onError(address, error, errorType, GattError.parseDfuRemoteError(error));
            if (deviceListener != null)
              deviceListener.onError(address, error, errorType, GattError.parseDfuRemoteError(error));
            break;
          default:
            if (globalListener != null)
              globalListener.onError(address, error, errorType, GattError.parse(error));
            if (deviceListener != null)
              deviceListener.onError(address, error, errorType, GattError.parse(error));
            break;
        }
      }
    }
  }
}

export class DfuServiceListenerHelper {
  private static mLogBroadcastReceiver: LogBroadcastReceiver;
  private static mProgressBroadcastReceiver: ProgressBroadcastsReceiver;

  /**
   * Registers the {@link DfuProgressListener}.
   * Registered listener will receive the progress events from the DFU service.
   *
   * @param context  the application context.
   * @param listener the listener to register.
   */
  public static registerProgressListener(listener: DfuProgressListener) {
    if (this.mProgressBroadcastReceiver == null) {
      this.mProgressBroadcastReceiver = new ProgressBroadcastsReceiver();

      //noinspection deprecation
      // LocalBroadcastManager.getInstance(context).registerReceiver(this.mProgressBroadcastReceiver, filter);
    }
    this.mProgressBroadcastReceiver.setProgressListener(listener);
  }

  /**
   * Registers the {@link DfuProgressListener}. Registered listener will receive the progress
   * events from the DFU service.
   *
   * @param context       the application context.
   * @param listener      the listener to register.
   * @param deviceAddress the address of the device to receive updates from (or null if any device).
   */
  public static registerProgressListenerByMac(listener: DfuProgressListener, deviceAddress: string) {
    if (this.mProgressBroadcastReceiver == null) {
      this.mProgressBroadcastReceiver = new ProgressBroadcastsReceiver();

      //noinspection deprecation
      // LocalBroadcastManager.getInstance(context).registerReceiver(mProgressBroadcastReceiver, filter);
    }
    this.mProgressBroadcastReceiver.setProgressListenerByMac(deviceAddress, listener);
  }

  /**
   * Unregisters the previously registered progress listener.
   *
   * @param context  the application context.
   * @param listener the listener to unregister.
   */
  public static unregisterProgressListener(listener: DfuProgressListener) {
    if (this.mProgressBroadcastReceiver != null) {
      let empty: boolean = this.mProgressBroadcastReceiver.removeProgressListener(listener);

      if (empty) {
        //noinspection deprecation
        // LocalBroadcastManager.getInstance(context).unregisterReceiver(mProgressBroadcastReceiver);
        this.mProgressBroadcastReceiver = null;
      }
    }
  }

  /**
   * Registers the {@link DfuLogListener}. Registered listener will receive the log events from the DFU service.
   *
   * @param context  the application context.
   * @param listener the listener to register.
   */
  public static registerLogListener(listener: DfuLogListener) {
    if (this.mLogBroadcastReceiver == null) {
      this.mLogBroadcastReceiver = new LogBroadcastReceiver();

      //noinspection deprecation
      // LocalBroadcastManager.getInstance(context).registerReceiver(mLogBroadcastReceiver, filter);
    }
    this.mLogBroadcastReceiver.setLogListener(listener);
  }

  /**
   * Registers the {@link DfuLogListener}. Registered listener will receive the log events from
   * the DFU service.
   *
   * @param context       the application context.
   * @param listener      the listener to register.
   * @param deviceAddress the address of the device to receive updates from (or null if any device).
   */
  public static registerLogListenerByMac(listener: DfuLogListener, deviceAddress: string) {
    if (this.mLogBroadcastReceiver == null) {
      this.mLogBroadcastReceiver = new LogBroadcastReceiver();

      //noinspection deprecation
      // LocalBroadcastManager.getInstance(context).registerReceiver(mLogBroadcastReceiver, filter);
    }
    this.mLogBroadcastReceiver.setLogListenerByMac(deviceAddress, listener);
  }

  /**
   * Unregisters the previously registered log listener.
   *
   * @param context  the application context.
   * @param listener the listener to unregister.
   */
  public static unregisterLogListener(listener: DfuLogListener) {
    if (this.mLogBroadcastReceiver != null) {
      let empty: boolean = this.mLogBroadcastReceiver.removeLogListener(listener);

      if (empty) {
        //noinspection deprecation
        // LocalBroadcastManager.getInstance(context).unregisterReceiver(mLogBroadcastReceiver);
        this.mLogBroadcastReceiver = null;
      }
    }
  }
}